package com.aliothservice.catfish.device.data;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import com.aliothservice.catfish.device.request.VehicleBindingRequestRequest;
import com.aliothservice.catfish.device.request.VehicleHumidityDocumentRequest;
import com.aliothservice.catfish.device.request.VehicleLocationDocumentRequest;
import com.aliothservice.catfish.device.request.VehicleTemperatureDocumentRequest;
import com.aliothservice.catfish.device.request.WarehouseBindingRequestRequest;
import com.aliothservice.catfish.product.data.VehiclePending;
import com.aliothservice.catfish.product.data.WarehousePending;

public class DeviceDataManager {
	
	public boolean vehicleBindingRequest(String phoneNumber, VehicleBindingRequestRequest req) {
		Query locateQuery = new Query();
		Criteria locateUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria locateIdCriteria = Criteria.where("id").is(req.getPendingId());
		Criteria locateAndCriteria = new Criteria();
		locateAndCriteria.andOperator(locateUserCriteria, locateIdCriteria);
		locateQuery.addCriteria(locateAndCriteria);
		
		VehiclePending pending = mongoTemplate.findOne(locateQuery, VehiclePending.class);
		if (null == pending) {
			logger.error("vehicleBindingRequest - failed to locate vehicle pending, id:" + req.getPendingId() + " user:" + phoneNumber);
			return false;
		}
		
		VehicleBindingRequest bindingRequest = new VehicleBindingRequest();
		bindingRequest.setUserId(phoneNumber);
		bindingRequest.setContactName(req.getContactName());
		bindingRequest.setContactPhoneNumber(req.getContactPhoneNumber());
		bindingRequest.setPendingId(req.getPendingId());
		bindingRequest.setPendingObject(pending);
		bindingRequest.setStatus(VehicleBindingRequest.STATUS_CREATED);
		bindingRequest.setCreationDate(new Date());
		mongoTemplate.save(bindingRequest);
		
		return true;
	}
	
	public boolean warehouseBindingRequest(String phoneNumber, WarehouseBindingRequestRequest req) {
		Query locateQuery = new Query();
		Criteria locateUserCriteria = Criteria.where("userId").is(phoneNumber);
		Criteria locateIdCriteria = Criteria.where("id").is(req.getPendingId());
		Criteria locateAndCriteria = new Criteria();
		locateAndCriteria.andOperator(locateUserCriteria, locateIdCriteria);
		locateQuery.addCriteria(locateAndCriteria);

		WarehousePending pending = mongoTemplate.findOne(locateQuery, WarehousePending.class);
		if (null == pending) {
			logger.error("warehouseBindingRequest - failed to locate warehouse pending, id:" + req.getPendingId() + " user:" + phoneNumber);
			return false;			
		}
		
		WarehouseBindingRequest bindingRequest = new WarehouseBindingRequest();
		bindingRequest.setUserId(phoneNumber);
		bindingRequest.setContactName(req.getContactName());
		bindingRequest.setContactPhoneNumber(req.getContactPhoneNumber());
		bindingRequest.setPendingId(pending.getId());
		bindingRequest.setPendingObject(pending);
		bindingRequest.setStatus(WarehouseBindingRequest.STATUS_CREATED);
		bindingRequest.setCreationDate(new Date());
		mongoTemplate.save(bindingRequest);
		
		return true;
	}
	
	public Map<String, Object> vehicleLocationDocument(String phoneNumber, VehicleLocationDocumentRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("orderId").is(req.getOrderId());
		Criteria userCriteria = Criteria.where("viewers").all(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria);
		query.addCriteria(andCriteria);
		LocationDocument document = mongoTemplate.findOne(query, LocationDocument.class);
		if (null == document) {
			return null;
		}
		
		Map<String, Object> map = document.toMap();
		Point point = new Point(req.getLongitude().doubleValue(), req.getLatitude().doubleValue());
		Distance radius = new Distance(req.getRadius().doubleValue(), Metrics.KILOMETERS);
		Circle circle = new Circle(point, radius);
		Query locationQuery = new Query();
		Criteria locationCriteria = Criteria.where("location").withinSphere(circle);
		Criteria orderCriteria = Criteria.where("orderId").is(req.getOrderId());
		Criteria locationAndCriteria = new Criteria();
		locationAndCriteria.andOperator(locationCriteria, orderCriteria);
		locationQuery.addCriteria(locationAndCriteria);
		List<LocationObject> locationObjects = mongoTemplate.find(locationQuery, LocationObject.class);
		if (locationObjects.size() > 0) {
			List<Map<String, Object>> locationList = new ArrayList<Map<String, Object>>();
			for (LocationObject locationObject : locationObjects) {
				locationList.add(locationObject.toMap());
			}
			map.put("locationObjects", locationList);
		}
		
		return map;
	}
	
	public Map<String, Object> vehicleTemperatureDocument(String phoneNumber, VehicleTemperatureDocumentRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("orderId").is(req.getOrderId());
		Criteria userCriteria = Criteria.where("viewers").all(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria);
		query.addCriteria(andCriteria);
		TemperatureDocument document = mongoTemplate.findOne(query, TemperatureDocument.class);
		if (null != document) {
			return document.toMap();
		}
		
		return null;
	}
	
	public Map<String, Object> vehicleHumidityDocument(String phoneNumber, VehicleHumidityDocumentRequest req) {
		Query query = new Query();
		Criteria idCriteria = Criteria.where("orderId").is(req.getOrderId());
		Criteria userCriteria = Criteria.where("viewers").all(phoneNumber);
		Criteria andCriteria = new Criteria();
		andCriteria.andOperator(idCriteria, userCriteria);
		query.addCriteria(andCriteria);
		HumidityDocument document = mongoTemplate.findOne(query, HumidityDocument.class);
		if (null != document) {
			return document.toMap();
		}
		
		return null;
	}
	
	@PostConstruct
	public void populatePredefinedData() {
		// create indexes
		mongoTemplate.indexOps(LocationObject.class).ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE));
	}
	
	@Autowired
	private MongoTemplate mongoTemplate;

	private static final Logger logger = LoggerFactory.getLogger(DeviceDataManager.class);
}
